package com.jml.task.lock.impl;

import com.jml.task.entity.RedisLockInfo;
import com.jml.task.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


@Component
@Slf4j
public class RedisLockImpl implements RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String redisLockKey = "jmlLock";
    /**
     * 缓存redis锁
     */
    private static Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();
    /**
     * 重试时间
     */
    private Long timeout = 3000L;
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    //分布式锁的可重入性
    @Override
    public boolean tryLock() {
        // 1.多个jvm执行setnx 命令 最终只有一个jvm能够成功
        // setnx
        Thread cuThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(cuThread);
        if (redisLockInfo != null && redisLockInfo.isState()) {
            // 这把锁可重入次数—+1
            log.info("<您在之前已经获取过锁，锁直接可重入>");
            return true;
        }

        // 重试机制  重试次数 或者 重试10s
        Long startTime = System.currentTimeMillis();
        //锁的过期时间
        Long lockExpire = 30000L;
        String lockId = UUID.randomUUID().toString();
        for (; ; ) {
            Boolean lock = null;//stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, lockId, lockExpire, TimeUnit.SECONDS);
            if (lock) {
                log.info("<获取锁成功>");
                lockCacheMap.put(cuThread, new RedisLockInfo(cuThread, lockExpire,lockId));
                // 开始续命监听
                return true;
            }
            // 控制一个超时的时间
            Long endTime = System.currentTimeMillis();
            if (endTime - startTime > timeout) {
                log.info("<重试的时间已经过了，不能够在继续重试啦>");
                return false;
            }
            // 休眠一会继续循环
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
        }

    }

    @Override
    public boolean releaseLock(String lockId) {
        // A线程创建的锁 B线程执行释放锁 是谁创建的锁，就应该给谁删除。 lua脚本实现删除
        String redisValue = stringRedisTemplate.opsForValue().get(redisLockKey);
        if(redisValue.equals(lockId)){
            return stringRedisTemplate.delete(redisLockKey);
        }else{
            return false;
        }

    }

    @Override
    public boolean releaseLock() {
        Thread thread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(thread);
        if(redisLockInfo==null){
            return false;
        }
        String redisValue = stringRedisTemplate.opsForValue().get(redisLockKey);
        if(redisValue.equals(redisLockInfo.getLockId())){
            return stringRedisTemplate.delete(redisLockKey);
        }else{
            return false;
        }
    }

    public RedisLockImpl() {
        //开始定时任务实现续命
        this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 5, TimeUnit.SECONDS);
    }

    /**
     * 当我们的获取锁的jvm业务执行时间>过期key的超时时间 应该实现续命：
     * 当key过期的时候：走事件回调到客户端。---时间在延长
     * 延长过期key 应该是提前的。通过定时任务提前延长过期key
     * 算法实现：
     * 开启一个定时任务，每隔一段时间检测获取到锁的线程，延长该过期key的时间
     */
    class LifeExtensionThread implements Runnable {

        @Override
        public void run() {
            lockCacheMap.forEach((k, lockInfo) -> {
                try {
                    // 如何判断当前线程还没有执行完毕？
                    Thread lockServiceThread = lockInfo.getLockThread();
                    if (lockServiceThread.isInterrupted()) {
                        log.info(">>当前线程已经被停止，不需要实现续命<<");
                        lockCacheMap.remove(k);
                        return;
                    }
                    // 需要控制续命多次，如果获取到锁的jvm 续命多次还是没有将业务逻辑执行完毕的情况下处理： 主动释放锁 事务会回滚
                    Integer lifeCount = lockInfo.getLifeCount();
                    if (lifeCount > 3) {
                        log.info(">>您已经续命了多次当前线程还没有释放锁，现在主动将该锁释放 避免死锁的问题");
                        // 1.事务回滚
                        // 2.释放锁 dele
                        releaseLock(lockInfo.getLockId());
                        // 3.将该线程主动停止
                        lockServiceThread.interrupt();
                        // 4.移除监听
                        lockCacheMap.remove(k);
                        return;
                    }
                    //提前实现续命 延长过期key的时间
                    stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
    /**
     * 续命线程两种实现方式
     * 1.全局续命 5s 10 s 20s 续命 缺点：存在 执行续命不精确。
     * 2.局部续命
     */
}
